<?php
namespace Libs;

class UpYun
{
    const VERSION            = '2.0';

    const ED_AUTO            = 'v0.api.upyun.com';
    const ED_TELECOM         = 'v1.api.upyun.com';
    const ED_CNC             = 'v2.api.upyun.com';
    const ED_CTT             = 'v3.api.upyun.com';
 
    const CONTENT_TYPE       = 'Content-Type';
    const CONTENT_MD5        = 'Content-MD5';
    const CONTENT_SECRET     = 'Content-Secret';

    // 缩略图
    const X_GMKERL_THUMBNAIL = 'x-gmkerl-thumbnail';
    const X_GMKERL_TYPE      = 'x-gmkerl-type';
    const X_GMKERL_VALUE     = 'x-gmkerl-value';
    const X_GMKERL_QUALITY   = 'x­gmkerl-quality';
    const X_GMKERL_UNSHARP   = 'x­gmkerl-unsharp';
    const X_GMKERL_EXIF      = 'x-gmkerl-exif-switch';
    
    protected $_config;
    //允许上传的最大容量 (KB)
    public $upload_max_size  = 2048;
    public $width = null;
    public $height = null;
    // 允许上传的文件类型，分为图片和文件两个类型,默认为images。
    public $upload_type = 'img';

    
    private $_timeout = 30;
    private     $_content_md5 = NULL;
    private     $_file_secret = NULL;
    protected   $protected_domain = array('img.taodudu.com.cn','taodudu.com');
    private     $_file_infos= NULL;
    protected   $_defaultConfig = array(
        'config'=>'default',
    );
    protected   $config = array(
        'default'=>array(
            'bucketname'=>'taodudu',
            'username'=>'ubten',
            'password'=>'123456.0.',
            'outurl'=>'http://img.taodudu.com.cn',
        )
    );
    
    protected $endpoint;
    /**
     * @var string: UPYUN 请求唯一id, 出现错误时, 可以将该id报告给 UPYUN,进行调试
     */
    private $x_request_id;
    
    public $error_code = NULL;
    
    public $error_message = array();
    
    public $last_file = NULL;
    /**
    * 初始化 UpYun 存储接口
    * @param array $config 空间名称 操作员名称 密码
    * @return null
    */
    
    public function __construct(array $config = array())
    {
        if(empty($config)){
            $config = $this->_defaultConfig;
        }
        $this->set_bucket($config['config']);
        $this->endpoint = self::ED_AUTO;
        if(defined('UPLOAD_MAX_SIZE')){
            $this->upload_max_size = UPLOAD_MAX_SIZE;
        }
    }
    public function set_bucket($config = 'default'){
        $bucket = $this->config[$config];
        foreach($bucket as $k=>$v){
            if($k == 'password'){
                $v = md5($v);
            }
            $k = '_'.$k;
            $this->$k = $v;
        }
    }
    /**
     * 获取当前SDK版本号
     */
    public function version() {
        return self::VERSION;
    }

    /** 
     * 创建目录
     * @param $path 路径
     * @param $auto_mkdir 是否自动创建父级目录，最多10层次
     *
     * @return void
     */
    public function mkdir($path, $auto_mkdir = false) {
        $headers = array('Folder' => 'true');
        if ($auto_mkdir){
            $headers['Mkdir'] = 'true';
            
        }
        return $this->_do_request('PUT', $path, $headers);
    }

    /**
     * 删除目录和文件
     * @param string $path 路径
     *
     * @return boolean
     */
    public function delete($path) {
        if(strpos($path, self::SITE_CDN) == 0){
            $path = str_ireplace(self::SITE_CDN,'',$path);
        }
        return $this->_do_request('DELETE', $path);
    }
    /**
     * 上传文件
     * @param string $path 存储路径
     * @param mixed $file 需要上传的文件，可以是文件流或者文件内容
     * @param boolean $auto_mkdir 自动创建目录
     * @param array $opts 可选参数
     */
    public function write( $file,$path = null, $auto_mkdir = true, $opts = null) {
        if (is_null($opts)) $opts = array();
        if (!is_null($this->_content_md5) || !is_null($this->_file_secret)) {
            if (!is_null($this->_content_md5)) $opts[self::CONTENT_MD5] = $this->_content_md5;
            if (!is_null($this->_file_secret)) $opts[self::CONTENT_SECRET] = $this->_file_secret;
        }
        // 保留图片的EXIF信息
        $opts[self::X_GMKERL_EXIF] = 'true';
        if (isset($opts[self::X_GMKERL_THUMBNAIL]) || isset($opts[self::X_GMKERL_TYPE])) {
            if (!isset($opts[self::X_GMKERL_QUALITY])) $opts[self::X_GMKERL_QUALITY] = 95;
            if (!isset($opts[self::X_GMKERL_UNSHARP])) $opts[self::X_GMKERL_UNSHARP] = 'true';
        }
        if ($auto_mkdir === true) {
            $opts['Mkdir'] = 'true';
        }
        $this->_file_infos = $this->_do_request('PUT', $path, $opts, $file);
        if(is_null($this->error_code) && $this->_file_infos){
            return $this->_outurl.$path;
        }else{
            return false;
        }
    }

    /**
     * 下载文件
     * @param string $path 文件路径
     * @param mixed $file_handle
     *
     * @return mixed
     */
    public function read($path, $file_handle = NULL) {
        return $this->_do_request('GET', $path, NULL, NULL, $file_handle);
    }

    public function upload($file,$outurl = true){
        $this->error_message = array();
        // 检测上传的文件是否错误
        if(isset($file['error']) && $file['error'] != 0){
           throw new \Exception('您没有上传任何图片');
           return false;
        }
        if(!is_null($this->width) && !is_null($this->height)){
            $this->check_volume($file['tmp_name']);
            
        }
        // 检测上传的文件格式
        if(isset($file['type']) && $this->check_type($file['type']) && $this->check_size($file['size'])){
            $path = $this->get_path($file['name']);
            
            $hander = fopen($file['tmp_name'], 'r');
            $result = $this->write($hander,$path);
            fclose($hander);
            unset($hander);
            if($result){
                return $result;
            }else{
                throw new \Exception('上传失败！');
            }
            
        }
        return false;
    }
    function get_path($filename,$ext = 'jpg'){
        $time = time();
        return '/'.date('Y',$time).'/'.date('md',$time).'/'.date('h',$time).'/'.md5(generate_password(8).serialize($filename)).'.'.$ext;
    }
    /**
     * cos.x 11.7.3
     * 将远程图片保存到本地
     */
    function save_remote_image_url($url){
    	if($url =  $this->_save_remote_image($url)){
    		return $url;
    	}else{
    		return '';
    	}
    }
    function _save_remote_image($url) {
            $file_ext = strtolower(pathinfo($url, PATHINFO_EXTENSION)); // 后缀名
            if (!in_array($file_ext, array('jpg','gif','png'))) {
                    return false;
            }
            if($baseUrl = parse_url($url)){
                if(in_array($baseUrl['host'], $this->protected_domain)){
                        return false;
                }
            }
            $tmp = $this->curl_get_contents($url);
            if (!$tmp) {
                    return false;
            }
            $ext = $this->get_file_ext($url);
            $path = $this->get_path($url,$ext);
            $file = $this->write($tmp,$path);
            return $file;
    }
    function __save_remote_image($img) {
            $tmp = $this->_save_remote_image($img[1]);
            if ($tmp) {
                    return "<img src=\"$tmp\"";
            }else{
                    return $img[0];
            }
    }
    function save_remote_image($html) {
            $preg = '~<img[^>]+?src="(http://[^"]+)"~i';
            return preg_replace_callback($preg, array($this, '__save_remote_image'), $html);
    }
    public function get_file_ext($name){
        $arr = explode('.',$name);
        return $arr[count($arr)-1];
    }
    public function check_volume($img){
        list($width,$height) = getimagesize($img);
        if($width == $this->width && $height == $this->height){
            return true;
        }
        throw new \Exception('您上传的图片大小尺寸不正确');
        return false;
    }
    /**
     * 
     */
    public function check_size($size = ''){
        if($size/1024 > $this->upload_max_size){
            throw new \Exception('您上传的图片大小超出限制'.($this->upload_max_size/1024).'MB');
            return false;
        }else{
            return true;
        }
    }
    /**
     * 检查上传的文件类型
     * @param type $type
     * @return boolean
     */
    public function check_type($type = ''){
        if($this->upload_type == 'img'){
            if(!in_array($type,array('image/pjpeg','image/jpeg','image/gif','image/x-png','image/png'))){
                throw new \Exception('上传的图片格式不正确，请上传 jpg/gif/png格式。');
            }else{
                return true;
            }
        }
        return false;
    }
    public function errors(){
        return $this->error_message;
    }
    /**
     * 获取目录文件列表
     *
     * @param string $path 查询路径
     *
     * @return mixed
     */
    public function ls($path = '/') {
        $rsp = $this->_do_request('GET', $path);

        $list = array();
        if ($rsp) {
            $rsp = explode("\n", $rsp);
            foreach($rsp as $item) {
                @list($name, $type, $size, $time) = explode("\t", trim($item));
                if (!empty($time)) {
                    $type = $type == 'N' ? 'file' : 'folder';
                }

                $item = array(
                    'name' => $name,
                    'type' => $type,
                    'size' => intval($size),
                    'time' => intval($time),
                );
                array_push($list, $item);
            }
        }

        return $list;
    }

    /**
     * 
     * @param string $path 目录路径
     * @return mixed
     */
    public function used($path = '/') {
        $rsp = $this->_do_request('GET', '/?usage');
        return floatval($rsp);
    }

    /**
     * 获取文件、目录信息
     *
     * @param string $path 路径
     *
     * @return mixed
     */
    public function info($path) {
        $rsp = $this->_do_request('HEAD', $path);

        return $rsp;
    }/*}}}*/

    /**
    * 连接签名方法
    * @param $method 请求方式 {GET, POST, PUT, DELETE}
    * return 签名字符串
    */
    private function sign($method, $uri, $date, $length){/*{{{*/
    //$uri = urlencode($uri);
            $sign = "{$method}&{$uri}&{$date}&{$length}&{$this->_password}";
            return 'UpYun '.$this->_username.':'.md5($sign);
    }/*}}}*/

    /**
     * HTTP REQUEST 封装
     * @param string $method HTTP REQUEST方法，包括PUT、POST、GET、OPTIONS、DELETE
     * @param string $path 除Bucketname之外的请求路径，包括get参数
     * @param array $headers 请求需要的特殊HTTP HEADERS
     * @param array $body 需要POST发送的数据
     *
     * @return mixed
     */
    protected function _do_request($method, $path, $headers = NULL, $body= NULL, $file_handle= NULL) {/*{{{*/
        $uri = "/{$this->_bucketname}{$path}";
        $ch = curl_init("http://{$this->endpoint}{$uri}");
        $_headers = array('Expect:');
        if (!is_null($headers) && is_array($headers)){
            foreach($headers as $k => $v) {
                array_push($_headers, "{$k}: {$v}");
            }
        }

        $length = 0;
		$date = gmdate('D, d M Y H:i:s \G\M\T');

        if (!is_null($body)) {
            if(is_resource($body)){
                fseek($body, 0, SEEK_END);
                $length = ftell($body);
                fseek($body, 0);

                array_push($_headers, "Content-Length: {$length}");
                curl_setopt($ch, CURLOPT_INFILE, $body);
                curl_setopt($ch, CURLOPT_INFILESIZE, $length);
            } else {
                $length = @strlen($body);
                array_push($_headers, "Content-Length: {$length}");
                curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
            }
        } else {
            array_push($_headers, "Content-Length: {$length}");
        }

        array_push($_headers, "Authorization: {$this->sign($method, $uri, $date, $length)}");
        array_push($_headers, "Date: {$date}");

        curl_setopt($ch, CURLOPT_HTTPHEADER, $_headers);
        curl_setopt($ch, CURLOPT_TIMEOUT, $this->_timeout);
        curl_setopt($ch, CURLOPT_HEADER, 1);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        //curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
		curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);

        if ($method == 'PUT' || $method == 'POST') {
			curl_setopt($ch, CURLOPT_POST, 1);
        } else {
			curl_setopt($ch, CURLOPT_POST, 0);
        }

        if ($method == 'GET' && is_resource($file_handle)) {
            curl_setopt($ch, CURLOPT_HEADER, 0);
			curl_setopt($ch, CURLOPT_FILE, $file_handle);
        }

        if ($method == 'HEAD') {
            curl_setopt($ch, CURLOPT_NOBODY, true);
        }

        $response = curl_exec($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        if ($http_code == 0){
            throw new \Exception('连接超时', $http_code);
        }

        curl_close($ch);

        $header_string = '';
        $body = '';

        if ($method == 'GET' && is_resource($file_handle)) {
            $header_string = '';
            $body = $response;
        } else {
            list($header_string, $body) = explode("\r\n\r\n", $response, 2);
        }
        $this->setXRequestId($header_string);
        if ($http_code == 200) {
            if ($method == 'GET' && is_null($file_handle)) {
                return $body;
            } else {
                $data = $this->_getHeadersData($header_string);
                return count($data) > 0 ? $data : true;
            }
        } else {
            $message = $this->_getErrorMessage($header_string);
            if (is_null($message) && $method == 'GET' && is_resource($file_handle)) {
                $message = 'File Not Found';
            }
            $this->set_error_info($message);
            throw new \Exception($message);
            return false;
        }
    }

    /**
     * 处理HTTP HEADERS中返回的自定义数据
     *
     * @param string $text header字符串
     *
     * @return array
     */
    private function _getHeadersData($text) {/*{{{*/
        $headers = explode("\r\n", $text);
        $items = array();
        foreach($headers as $header) {
            $header = trim($header);
			if(stripos($header, 'x-upyun') !== False){
				list($k, $v) = explode(':', $header);
                $items[trim($k)] = in_array(substr($k,8,5), array('width','heigh','frame')) ? intval($v) : trim($v);
			}
        }
        return $items;
    }

    /**
     * 获取返回的错误信息
     *
     * @param string $header_string
     *
     * @return mixed
     */
    private function _getErrorMessage($header_string) {
        list($status, $stash) = explode("\r\n", $header_string, 2);
        list($v, $code, $message) = explode(" ", $status, 3);
        return $message;
    }

    private function setXRequestId($header_string) {
        preg_match('~^X-Request-Id: ([0-9a-zA-Z]{32})~ism', $header_string, $result);
        $this->x_request_id = isset($result[1]) ? $result[1] : '';
    }

    public function getXRequestId() {
        return $this->x_request_id;
    }
    private function set_error_info($message){
        $errors = array(
            'Forbidden'=>array('code'=>501,'没有权限上传 图片'),
            'Bad Request'=>array('code'=>400,'desc'=>'错误请求(如 URL 缺少空间名)'),
            'Unauthorized'=>array('code'=>401,'desc'=>'访问未授权'),
            'Sign error'=>array('code'=>401,'desc'=>'签名错误(操作员和密码,或签名格式错误)'),
            'Need Date Header'=>array('code'=>401,'desc'=>'发起的请求缺少 Date 头信息'),
            'Date offset error'=>array('code'=>401,'desc'=>'发起请求的服务器时间错误，请检查服务器时间是否与世界时间一致'),
            'Not Access'=>array('code'=>403,'desc'=>'权限错误(如非图片文件上传到图片空间)'),
            'File size too max'=>array('code'=>403,'desc'=>'单个文件超出大小(1 GB 以内)'),
            '403'=>array('code'=>403,'desc'=>'图片类空间错误码，非图片文件或图片文件格式错误。针对图片空间只允许上传 jpg/png/gif/bmp/tif 格式。'),
            'Picture Size too max'=>array('code'=>403,'desc'=>'图片类空间错误码，图片尺寸太大。针对图片空间，图片总像素在 200000000 以内。'),
            'Bucket full'=>array('code'=>403,'desc'=>'空间已用满'),
            'Bucket blocked'=>array('code'=>403,'desc'=>'空间被禁用,请联系管理员'),
            'User blocked'=>array('code'=>403,'desc'=>'操作员被禁用'),
            'Image Rotate Invalid Parameters'=>array('code'=>403,'desc'=>'图片旋转参数错误'),
            'Image Crop Invalid Parameters'=>array('code'=>403,'desc'=>'图片裁剪参数错误'),
            'Not Found'=>array('code'=>404,'desc'=>'获取文件或目录不存在；上传文件或目录时上级目录不存在'),
            'Not Acceptable(path)'=>array('code'=>406,'desc'=>'目录错误（创建目录时已存在同名文件；或上传文件时存在同名目录)'),
            'System Error'=>array('code'=>503,'desc'=>'系统错误')
        );
        $error = $errors[$message];
        if(!empty($error)){
            $this->error_code = $error['code'];
            $this->error_message = $error['desc'];
        }else{
            $this->error_code = 0;
        }
        
        return false;
    }
    /**
    * 切换 API 接口的域名
    *
    * @deprecated
    * @param $domain {默然 v0.api.upyun.com 自动识别, v1.api.upyun.com 电信, v2.api.upyun.com 联通, v3.api.upyun.com 移动}
    * return null;
    */
    public function setApiDomain($domain){
            $this->endpoint = $domain;
    }

    /**
    * 设置待上传文件的 Content-MD5 值（如又拍云服务端收到的文件MD5值与用户设置的不一致，将回报 406 Not Acceptable 错误）
    *
    * @deprecated
    * @param $str （文件 MD5 校验码）
    * return null;
    */
    public function setContentMD5($str){
            $this->_content_md5 = $str;
    }

    /**
    * 设置待上传文件的 访问密钥（注意：仅支持图片空！，设置密钥后，无法根据原文件URL直接访问，需带 URL 后面加上 （缩略图间隔标志符+密钥） 进行访问）
    * 如缩略图间隔标志符为 ! ，密钥为 bac，上传文件路径为 /folder/test.jpg ，那么该图片的对外访问地址为： http://空间域名/folder/test.jpg!bac
    *
    * 
    * @param $str （文件 MD5 校验码）
    * return null;
    */
    public function setFileSecret($str){
            $this->_file_secret = $str;
    }
    function curl_get_contents($url)   
    {   
        $ch = curl_init();   
        curl_setopt($ch, CURLOPT_URL, $url);            //设置访问的url地址   
        //curl_setopt($ch,CURLOPT_HEADER,1);            //是否显示头部信息   
        curl_setopt($ch, CURLOPT_TIMEOUT, 5);           //设置超时     
        curl_setopt($ch,CURLOPT_FOLLOWLOCATION,1);      //跟踪301   
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);        //返回结果   
        $r = curl_exec($ch);   
        curl_close($ch);   
        return $r;   
    }
}
